import random
import population
import sys
from math import floor, ceil, fabs, exp
from copy import deepcopy

class SelectParents:

	def roulette(self,pop):	#roleta
		"""
		Selection by Fitness Proportional Selection.
		The probability of being chosed is equal to their fitness
		related to the total population fitness.
		"""
		
		pop.sort_individuals() #ordena a pop!
		total_fitness = reduce((lambda x,y: x+y), map((lambda y: fabs(y.get_fitness())),pop))
		
		def pick_one(pop):
			
			roll = random.random()*total_fitness # random num de 0-total_fitness
			i = -1 #index do escolhido
			while roll>0:
				i+=1
				roll = roll - fabs(pop[i].get_fitness())
			return pop[i]
		
		new_pop=population.Population("Parameter Copy", pop=pop) #faz new population
		for i in range(len(pop)):
			new_pop.add_individual(pick_one(pop))
		
		return new_pop

	def roulette_select(self, arity,num_fighters,loosers_hype,high_prob):
		return (lambda pop: self.roulette(pop))
	
	def stochastic_universal_sampling(self,pop):	#amostragem universal estocastica
		"""
		Selection by Fitness Proportional Selection but the chosen
		ones are all equally distributed in the sorted population.
		"""
		
		pop.sort_individuals() #ordena a pop!
		num_ptrs = 5
		
		def  get_ptrs(n_ptrs):
			
			merda = []
			for i in range(len(pop)):
				merda.append(pop[i].get_fitness())
				
			total_fitness = reduce((lambda x,y: x+y), merda) #total_fitness of pop
			start = random.random()*(total_fitness/n_ptrs) # random num de 0-total_fitness/n_ptrs
			ptrs = [start+i*(total_fitness/n_ptrs) for i in range(n_ptrs)]
			return ptrs
		
		def pick_one(flag):
			ptr = 0
			i = 0
			for p in pop:
				if ptr < flag and ptr+fabs(p.get_fitness()) > flag:
					return p
				ptr += fabs(p.get_fitness())
				i+=1
		
		new_pop=population.Population("Parameter Copy", pop=pop) #faz new population
		
		for i in range(len(pop)/num_ptrs):
			#use the ptrs to get individuals
			for p in lol:
				new_pop.add_individual(pick_one(p))

		if len(pop)%num_ptrs>0:
			for p in get_ptrs(len(pop)%num_ptrs): #rest
				new_pop.add_individual(pick_one(p))
		
		return new_pop


	def stochastic_universal_sampling_select(self,arity,num_fighters,loosers_hype,high_prob):
		return (lambda pop: self.stochastic_universal_sampling(pop))

	def tournament(self,pop,num_fighters=2,loosers_hype=0):	## DONE ##
		"""
		Seclection by tourneament.
		Given a population pop and a number of fighters num_fighters, returns
		the strongest element between two of them if a random number>loosers_hype.
		The process continues until all the fights are over.
		"""
	
		def pick_one(pop,num_fighters,loosers_hype):
			
			winer = random.choice(pop)
			for i in range(num_fighters-1):
				aux = random.choice(pop)
				[b,w] = winer.best_worst(aux)
				if random.random()>loosers_hype: #when hype is 0: always true -> not stochastic
					winer = b
				else:
					winer = w
			
			return winer
		
		new_pop=population.Population("Parameter Copy", pop=pop) #faz new population
		for i in range(len(pop)):
			new_pop.add_individual(pick_one(pop, num_fighters, loosers_hype))

		return new_pop

	def tournament_select(self,arity,num_fighters=2,loosers_hype=0,high_prob=0):
		return (lambda pop: self.tournament(pop,num_fighters,loosers_hype))

	def linear_ranking(self, s, N):
		p = []
		for i in range(0,N):
			p.append(((2.0-s)/N + (2.0*i*(s-1.0))/(N*(N-1.0))))
		return p
	
	def exponential_ranking(self, s, N):
		p = []
		sum = 0
		for i in range(0, N):
			p.append(1-exp(-i))
			sum+=1-exp(-i)
		
		for i in range(0, N):
			p[i] = p[i]/sum

		return p

	def order(self,pop,higher_prob,prob):	## DONE ##
		"""
		Selection by order.
		The best element has a probability of being chosen of prob_first.
		The other ones folow that probability as function.
		The functions maybe - exponential ou linear
		"""
		probs = prob(higher_prob, len(pop))
		pop.sort_individuals()
		
		def pick_one(pop):
			roll = random.random() # random num de 0-total_fitness
			i = -1 #index do escolhido
			while roll>0:
				i+=1
				roll = roll - probs[i]
			return pop[i]
		
		new_pop=population.Population("Parameter Copy", pop=pop) #faz new population
		
		for i in range(len(pop)):
			new_pop.add_individual(pick_one(pop))
			
		return new_pop
			

	def order_select_linear(self,arity,num_fighters,loosers_hype,high_prob):
		return (lambda pop: self.order(pop,high_prob,self.linear_ranking))

	def order_select_exponential(self,arity,num_fighters,loosers_hype,high_prob):
		return (lambda pop: self.order(pop,high_prob,self.exponential_ranking))
	

class SelectSurvivors:

	def __init__(self):
		pass
	
	#elitismo em tudo
	def gerational(self,pop_sons,pop_parents,elitism=0):
		"""
		Gerational Selection.
		Only the the sons pass to the next generation.
		If elitism > 0 thats the number of better parents that stay in the population.
		"""
		join_pop = pop_sons+pop_parents
		
		result_pop = population.Population("Parameter Copy", pop=pop_sons)
		
		join_pop.sort_individuals()
		
		for i in range(elitism):
			result_pop.add_individual(join_pop[i])
		for i in range(len(pop_sons)-elitism):
			result_pop.add_individual(pop_sons[i])
		
		return result_pop
	
	def gerational_select(self,elitism,percentage):
		return (lambda ps,pp: self.gerational(ps,pp,elitism))


	def chomp(self,pop_sons,pop_parents,elitism=0):
		""" Selection by chomp. """
		
		join = pop_sons+pop_parents
		join.sort_individuals()
		
		result_pop = population.Population("Parameter Copy", pop=pop_sons)
		
		for i in range(len(join)/2):
			result_pop.add_individual(join[i])
		
		return result_pop
	
	def chomp_select(self,elitism,percentage):
		return (lambda ps,pp: self.chomp(ps,pp,elitism))


	def best_worst(self,pop_sons,pop_parents,perc): ## perc = [percentagem_pop_k_sao melhores, percentagem dos melhores k passam]
		
		join_pop=pop_sons+pop_parents
		join_pop.sort_individuals()
		
		beasts = perc[0]*perc[1]
		num_bests_pass = int(floor(len(join_pop)*beasts))
		result_pop = population.Population("Parameter Copy", pop=join_pop)
		
		for i in range(num_bests_pass):
			result_pop.add_individual(join_pop[i])
		
		first_weak = int(ceil(perc[0]*len(join_pop)))
		num_worst_pass = int(len(pop_sons)-num_bests_pass)
		for i in range(num_worst_pass):
			result_pop.add_individual(join_pop[int(i+first_weak)])
		
		return result_pop


	def best_worst_select(self,elitism,percentages):
		return (lambda ps,pp: self.best_worst(ps,pp,percentages))